#include "gltexture.h"

#include <QDebug>
#include <assert.h>

QVector<GLuint> GLTexture::activeTextureIds = QVector<GLuint>(8); // 8 is the minimum value specified by opengl

GLTexture::GLTexture(const unsigned char *data, unsigned texWidth, unsigned texHeight, FormatValue format, TargetValue target)
    :   _texWidth(texWidth)
    ,   _texHeight(texHeight)
    ,   _format(format)
    ,   _target(target)
{
    //use tightly packed data
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    //generate a texture object
    glGenTextures(1, &_textureId);
    //bind the texture object
    glBindTexture(target, _textureId);
    //load the texture
    glTexImage2D(target, 0, _format, _texWidth, _texHeight, 0, _format, GL_UNSIGNED_BYTE, data);
    //set the filtering mode
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}

GLTexture::~GLTexture()
{
    glDeleteTextures(1, &_textureId);
}

/**
 * @brief Adds level manually to the mipmap levels
 * @param level Specifies the level-of-detail number. Level 0 is the base image
 * level. Level n is the nth mipmap reduction image.
 * @param width Specifies the width of the texture image. All implementations
 * support 2D texture images that are at least 64 texels wide and cube-mapped
 * texture images that are at least 16 texels wide.
 * @param height Specifies the height of the texture image All implementations
 * support 2D texture images that are at least 64 texels high and cube-mapped
 * texture images that are at least 16 texels high.
 * @param pixels Specifies a pointer to the image data in memory.
 */
void GLTexture::addMipMapLevel(GLint level, GLsizei width, GLsizei height, GLubyte *pixels)
{
    glTexImage2D(_target, level, _format, width, height, 0, _format, GL_UNSIGNED_BYTE, pixels);
}

/**
 * @brief Selects the active texture unit.
 * @param asTextureUnit
 * @see bind()
 */
void GLTexture::activate(int asTextureUnit)
{
    assert(asTextureUnit < activeTextureIds.size());
    glActiveTexture(GL_TEXTURE0 + asTextureUnit);
    activeTextureIds.replace(asTextureUnit, _textureId);
}

/**
 * @brief Bind this texture to its texturing target.
 *
 * The bind function lets you use this GLTexture for as long as it is the bound
 * texture. Subsequent calls to bind on another texture break the binding.
 *
 * When a texture is first bound, it assumes the specified target: A texture
 * first bound to GLTexture::Target2D becomes a two-dimensional texture and a
 * texture first bound to GLTexture::TargetCubeMap becomes a cube-mapped
 * texture. The state of a two-dimensional texture immediately after it is
 * first bound is equivalent to the state of the default GLTexture::Target2D at
 * GL initialization, and similarly for cube-mapped textures.
 */
void GLTexture::bind()
{
    glBindTexture(_target, _textureId);
}

/**
 * @brief Sets the minifying function for this GLTexture. Note for this call to
 * be effective, this GLTexture must be bound to the active texture unit (see
 * activate() and bind()).
 *
 * The texture minifying function is used whenever the pixel being
 * textured maps to an area greater than one texture element. There are six
 * defined minifying functions. Two of them use the nearest one (Nearest) or
 * nearest four (Linear) texture elements to compute the texture value. The
 * other four use mipmaps. This call affects the active texture map (see
 * activate()).
 *
 * The semantics of the enumerated MinificationFilterValues are:
 * - MinificationNearest: Returns the value of the texture element that is
 * nearest (in Manhattan distance) to the center of the pixel being textured.
 * - MinificationLinear: Returns the weighted average of the four texture
 * elements that are closest to the center of the pixel being textured.
 * - MinificationNearestMipMapNearest: Chooses the mipmap that most closely
 * matches the size of the pixel being textured and uses the GL_NEAREST
 * criterion (the texture element nearest to the center of the pixel) to
 * produce a texture value.
 * - MinficationLinearMipMapNearest: Chooses the mipmap that most closely
 * matches the size of the pixel being textured and uses the GL_LINEAR
 * criterion (a weighted average of the four texture elements that are closest
 * to the center of the pixel) to produce a texture value.
 * - MinificationNearestMipMapLinear: Chooses the two mipmaps that most closely
 * match the size of the pixel being textured and uses the GL_NEAREST criterion
 * (the texture element nearest to the center of the pixel) to produce a
 * texture value from each mipmap. The final texture value is a weighted
 * average of those two values. This is the default value.
 * - MinificationLinearMipMapLinear: Chooses the two mipmaps that most closely
 * match the size of the pixel being textured and uses the GL_LINEAR criterion
 * (a weighted average of the four texture elements that are closest to the
 * center of the pixel) to produce a texture value from each mipmap. The final
 * texture value is a weighted average of those two values.
 *
 * __N.B. If this texture is accessed from a vertex or fragment shader and the
 * minificationFilter is set to one of the values requiring mipmaps (i.e.,
 * MinificationNearestMipMapNearest, MinificationLinearMipMapNearest,
 * MinificationNearestMipMapLinear or MinificationLinearMipMapLinear), the
 * following caveats apply:__
 * - if the dimensions of this texture do not follow the proper sequence for
 * mipmaps (see addMipMapLevel()), this texture image unit will return
 * (R, G, B, A) = (0, 0, 0, 1).
 * - if there are fewer texture images defined than needed, this texture image
 * unit will return (R, G, B, A) = (0, 0, 0, 1),
 * - if the width and height of a texture are not powers of two the texture
 * image unit will return (R, G, B, A) = (0, 0, 0, 1).
 * @param value a FilterValue enum value. Defaults to
 * MinificationNearestMipMapLinear.
 * @param target the target texture unit. Defaults to Target2D.
 */
void GLTexture::setMinificationFilter(MinificationFilterValue value)
{
    glTexParameteri(_target, GL_TEXTURE_MIN_FILTER, value);
}

/**
 * @brief The texture magnification function is used when the pixel being
 * textured maps to an area less than or equal to one texture element. It sets
 * the texture magnification function to either MagnificationNearest or
 * MagnificationLinear (see below).
 *
 * MagnificationNearest is generally faster than MagnificationLinear, but it
 * can produce textured images with sharper edges because the transition
 * between texture elements is not as smooth. The default value of
 * MagnificationFilter is MagnificationLinear.
 * @param value One of MagnificationNearest or MagnificationLinear.
 * MagnificationNearest returns the value of the texture element that is
 * nearest (in Manhattan distance) to the center of the pixel being textured.
 * MagnificationLinear returns the weighted average of the four texture
 * elements that are closest to the center of the pixel being textured.
 */
void GLTexture::setMagnificationFilter(GLTexture::MagnificationFilterValue value)
{
    glTexParameteri(_target, GL_TEXTURE_MAG_FILTER, value);
}

/**
 * @brief Sets the wrap parameter for texture coordinate s in an (s, t, w, r)
 * texture coordinate system.
 *
 * ClampToEdge causes s coordinates to be clamped to the range
 * [ 1/2N, 1 - 1/2N ] where N is the size of the texture in the direction of
 * clamping. Repeat causes the integer part of the s coordinate to be ignored;
 * the GL uses only the fractional part, thereby creating a repeating pattern.
 * MirroredRepeat causes the s coordinate to be set to the fractional part of
 * the texture coordinate if the integer part of s is even; if the integer part
 * of s is odd, then the s texture coordinate is set to 1 - frac(s), where
 * frac(s) represents the fractional part of s.
 *
 * __N.B. If the width or height values are not a power of two and the given
 * wrap parameter value is not WrapClampToEdge the texture unit will return
 * (R, G, B, A) = (0, 0, 0, 1).
 * @param value one of the WrapValue enumerated values. Defaults to Repeat.
 */
void GLTexture::setSCoordinateWrap(GLTexture::WrapValue value, GLTexture::TargetValue target)
{
    glTexParameteri(target, GL_TEXTURE_WRAP_S, value);
}

/**
 * @brief GLTexture::setTCoordinateWrap
 * @see GLTexture::setSCoordinateWrap
 * @param value
 */
void GLTexture::setTCoordinateWrap(GLTexture::WrapValue value, GLTexture::TargetValue target)
{
    glTexParameteri(target, GL_TEXTURE_WRAP_T, value);
}

void GLTexture::setMaxTextureUnits(int maxTextureUnits)
{
    if (activeTextureIds.size() != maxTextureUnits)
    {
        activeTextureIds.resize(maxTextureUnits);
    }
}

